package org.bridgen.test.compare;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 
 * @author Emma Söderberg <emma add cs.lth.se>
 *
 */

public class Forest {
	protected ArrayList<Tree> treeList;
	protected Tree topTree; // The top tree

	public static Forest create(ArrayList<Tree> treeList) {
		if (treeList.size() == 0) {
			return empty();
		}
		return new Forest(treeList);
	}

	protected Forest(ArrayList<Tree> treeList) {
		this.treeList = treeList;
		forestCache = new HashMap<Interval, HashMap<Interval, Integer>>();
	}

	public void setTopTree(Tree tree) {
		this.topTree = tree;
	}
	
	public Tree tree(int i) {
		return treeList.get(i-1);
	}
	public int degree() {
		return treeList.size();
	}

	public void postOrderList(ArrayList<Tree> list) {
		for (int i = 0; i < treeList.size(); i++) {
			treeList.get(i).postOrderList(list);
		}	
	}

	// Cache 

	protected HashMap<Interval, HashMap<Interval, Integer>> forestCache;

	public boolean hasValueInCacheFor(Forest f2) {
		return hasValueInCacheFor(fullInterval(), f2.fullInterval());
	}

	public int getCachedValue(Forest f2) {
		return getCachedValue(fullInterval(), f2.fullInterval());
	}

	public void addCachedValue(Forest f2, int value) {
		addCachedValue(fullInterval(), f2.fullInterval(), value);
	}

	public boolean hasValueInCacheFor(Interval f1, Interval f2) {
		HashMap<Interval, Integer> intervalMap = forestCache.get(f1);
		if (intervalMap == null) {
			return false;
		}
		Integer value = intervalMap.get(f2);
		return value != null;
	}

	public int getCachedValue(Interval f1, Interval f2) {
		HashMap<Interval, Integer> intervalMap = forestCache.get(f1);
		Integer value = intervalMap.get(f2);
		return value.intValue();
	}

	public void addCachedValue(Interval f1, Interval f2, int value) {
		if (hasValueInCacheFor(f1,f2)){
//			System.out.println("Unnecessary caching!");
		}
		HashMap<Interval, Integer> intervalMap = forestCache.get(f1);
		if (intervalMap == null) {
			intervalMap = new HashMap<Interval, Integer>();
			forestCache.put(f1, intervalMap);
		} 
		/* DEBUG System.out.println("Caching distance(" + f1 + "," + f2 + ")=" + value); */
		intervalMap.put(f2, value);
	}

	// Empty

	private static final Empty empty = new Empty();
	public static Empty empty() {
		return empty;
	}

	public static class Empty extends Forest {
		private Empty() {
			super(new ArrayList<Tree>());
		}
		public String toString() {
			return "empty-forest";
		}
		public Interval fullInterval() {
			return new EmptyInterval();
		}
		public Interval interval(int start, int end) {
			if (start != 1 || end != 0) {
				throw new Error("Empty interval only has a full (empty) interval");
			}
			return fullInterval();
		}
	}
	
	// Print 
	
	public String toString() {
		return ((topTree == null) ? "null" : topTree.toString()) + ".forest";
	}

	// Interval
	
	protected Interval fullInterval;
	
	public Interval fullInterval() {
		if (fullInterval == null) {
			fullInterval = new Interval(this, 1, degree());
		}
		return fullInterval; 
	}

	public Interval interval(int start, int end) {
		return new Interval(this, start, end);
	}

	public static class Interval {
		protected final Forest forest;
		protected final int start;
		protected final int end;
		
		private int hashCode;

		private Interval(Forest forest, int start, int end) {
			this.forest = forest;
			this.start = start;
			this.end = end;
		}

		public boolean equals(Object obj) {
			if (obj instanceof Forest.Interval) {
				Interval f2 = (Interval)obj;
				return f2.forest == forest &&
					f2.start == start && 
					f2.end == end;
			}
			return super.equals(obj);
		}

		// Print
		
		public String toString() {
			return forest.toString() + "[" + start + "-" + end + "]";
		}
		
		// Hash
		
		public int hashCode() {
			/*
			 * A variant suggusted by LK
			  final int prime = 31;
			  int result = 1;
			  result = prime * result + end;
			  result = prime * result
			           + ((forest.topTree == null) ? 0 : forest.topTree.label().hashCode());
			  result = prime * result + start;
			  return result;
			*/
			
			if (hashCode == 0) { 
				hashCode = toString().hashCode();
			}
			return hashCode;
		}
	}
	
	public static class EmptyInterval extends Interval {
		public EmptyInterval() {
			super(Forest.empty(), 0, 0);
		}
		public String toString() {
			return forest.toString() + "[empty-interval]";
		}
	}
}
